package solver

import (
	"log"
)

func (g *Grid) UniqueRectangleType1() (used bool) {
	for i, row := range g.rows {
		pairs := getPairs(row)
		for _, pair := range pairs {
			ok, cell, cand := g.verifyUniqueRectangleType1(i, pair)
			if ok {
				g.solveOneCell(cell.rowNum, cell.colNum, cand+1)
				log.Printf("r%v%vc%v%v r%vc%v %v Unique Rectangle Type1",
					i, cell.rowNum, pair[0], pair[1],
					cell.rowNum, cell.colNum, cand+1)
				return true
			}
		}
	}
	return
}

func getPairs(row House) (pairs [][]int) {
	for i, cell1 := range row.cells {
		if cell1.candCount != 2 {
			continue
		}
		for j, cell2 := range row.cells[i+1:] {
			if cell2.candCount != 2 {
				continue
			}
			if candAnd(cell1.cands, cell2.cands) != cell1.cands {
				continue
			}
			pairs = append(pairs, []int{i, j + i + 1})
			break
		}
	}
	return
}
func (g *Grid) verifyUniqueRectangleType1(rowNum int, pair []int) (ok bool, cell *Cell, cand int) {
	cands := g.rows[rowNum].cells[pair[0]].cands
	for i, row := range g.rows {
		if i == rowNum {
			continue
		}
		if !g.sameBoxes([]int{i, rowNum}, pair) {
			continue
		}

		if candAnd(cands, row.cells[pair[0]].cands) != cands {
			continue
		}
		if candAnd(cands, row.cells[pair[1]].cands) != cands {
			continue
		}

		if row.cells[pair[0]].candCount == 2 && row.cells[pair[1]].candCount == 3 {
			ok = true
			cell = row.cells[pair[1]]
			cand = intsFromBools(candXor(cands, row.cells[pair[1]].cands))[0]
			return
		}

		if row.cells[pair[0]].candCount == 3 && row.cells[pair[1]].candCount == 2 {
			ok = true
			cell = row.cells[pair[0]]
			cand = intsFromBools(candXor(cands, row.cells[pair[0]].cands))[0]
			return
		}
	}
	return
}

func (g *Grid) sameBoxes(rowNums, colNums []int) bool {
	return rowNums[0]/3 == rowNums[1]/3 || colNums[0]/3 == colNums[1]/3
}

func (g *Grid) UniqueRectangleType2() (used bool) {
	if g.UniqueRectangleType2Lines(g.rows) {
		return true
	}

	if g.UniqueRectangleType2Lines(g.cols) {
		return true
	}

	return
}

func (g *Grid) UniqueRectangleType2Lines(lines [9]House) (used bool) {
	for i, line1 := range lines {
		pairs := getPairs(line1)

		for _, pair := range pairs {
			for j, line2 := range lines {
				if j == i {
					continue
				}
				if !g.sameBoxes([]int{i, j}, pair) {
					continue
				}

				ok, cells, cand := g.verifyUniqueRectangleType2(line1, pair, line2)
				if !ok {
					continue
				}
				if g.clearupSeenFromCells(cand, cells) {
					used = true
					logUniqueRectangleType2([]int{i, j}, pair, line2, cand+1)
					return
				}
			}
		}
	}
	return
}

func logUniqueRectangleType2(lineNums, cellNums []int, line House, value int) {
	if line.HouseType() == 0 {
		log.Printf("r%v%vc%v%v r%v %v Unique Rectangle Type2",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1],
			line.cells[0].rowNum, value)
	} else {
		log.Printf("c%v%vr%v%v c%v %v Unique Rectangle Type2",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1],
			line.cells[0].colNum, value)
	}
}

func (g *Grid) verifyUniqueRectangleType2(line1 House, pair []int, line2 House) (ok bool, cells []*Cell, cand int) {
	cands := line1.cells[pair[0]].cands
	cells = []*Cell{line2.cells[pair[0]], line2.cells[pair[1]]}
	if cells[0].candCount != 3 || cells[1].candCount != 3 {
		return
	}
	if candAnd(cands, cells[0].cands) != cands || candAnd(cands, cells[1].cands) != cands {
		return
	}

	if candAnd(cells[0].cands, cells[1].cands) != cells[1].cands {
		return
	}

	cand = intsFromBools(candXor(cands, cells[0].cands))[0]
	ok = true
	return
}

func (g *Grid) UniqueRectangleType3A() (used bool) {
	if g.UniqueRectangleType3ALines(g.rows) {
		return true
	}

	if g.UniqueRectangleType3ALines(g.cols) {
		return true
	}

	return
}

func (g *Grid) UniqueRectangleType3ALines(lines [9]House) (used bool) {
	for i, line1 := range lines {
		pairs := getPairs(line1)

		for _, pair := range pairs {
			for j, line2 := range lines {
				if j == i {
					continue
				}
				if !g.sameBoxes([]int{i, j}, pair) {
					continue
				}

				ok, cells, cands := g.verifyUniqueRectangleType3A(line1, pair, line2)
				if !ok {
					continue
				}
				if g.clearupCandsSeenFromCells(cands, cells) {
					used = true
					logUniqueRectangleType3A([]int{i, j}, pair, cells, cands)
					return
				}
			}
		}
	}
	return
}

func logUniqueRectangleType3A(lineNums, cellNums []int, cells []*Cell, cands []int) {
	if cells[0].rowNum == cells[1].rowNum {
		log.Printf("r%v%vc%v%v r%v r%vc%v %v%v Unique Rectangle Type3A",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1], cells[0].rowNum,
			cells[2].rowNum, cells[2].colNum, cands[0]+1, cands[1]+1)
	} else {
		log.Printf("c%v%vr%v%v c%v r%vc%v %v%v Unique Rectangle Type3A",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1], cells[0].colNum,
			cells[2].rowNum, cells[2].colNum, cands[0]+1, cands[1]+1)
	}
}

func (g *Grid) verifyUniqueRectangleType3A(line1 House, pair []int, line2 House) (ok bool, cells []*Cell, cands []int) {
	biCands := line1.cells[pair[0]].cands
	cells = []*Cell{line2.cells[pair[0]], line2.cells[pair[1]]}
	if cells[0].candCount != 3 || cells[1].candCount != 3 {
		return
	}
	if candAnd(biCands, cells[0].cands) != biCands || candAnd(biCands, cells[1].cands) != biCands {
		return
	}

	boolCands := candXor(cells[0].cands, cells[1].cands)
	cands = intsFromBools(boolCands)
	if len(cands) != 2 {
		return
	}

	if cells[0].rowNum == cells[1].rowNum {
		for _, cell := range g.rows[cells[0].rowNum].cells {
			if candAnd(boolCands, cell.cands) == boolCands {
				cells = append(cells, cell)
				ok = true
				return
			}
		}
	}
	if cells[0].colNum == cells[1].colNum {
		for _, cell := range g.cols[cells[0].colNum].cells {
			if candAnd(boolCands, cell.cands) == boolCands {
				cells = append(cells, cell)
				ok = true
				return
			}
		}
	}

	if cells[0].boxNum == cells[1].boxNum {
		for _, cell := range g.boxes[cells[0].boxNum].cells {
			if candAnd(boolCands, cell.cands) == boolCands {
				cells = append(cells, cell)
				ok = true
				return
			}
		}
	}

	return
}

func (g *Grid) UniqueRectangleType3B() (used bool) {
	if g.UniqueRectangleType3BLines(g.rows) {
		return true
	}

	if g.UniqueRectangleType3BLines(g.cols) {
		return true
	}

	return
}

func (g *Grid) UniqueRectangleType3BLines(lines [9]House) (used bool) {
	for i, line1 := range lines {
		pairs := getPairs(line1)

		for _, pair := range pairs {
			for j, line2 := range lines {
				if j == i {
					continue
				}
				if !g.sameBoxes([]int{i, j}, pair) {
					continue
				}

				ok, cell, cands := g.verifyUniqueRectangleType3B(line1, pair, line2)
				if !ok {
					continue
				}
				if cell.removeCandBut(cands) {
					used = true
					logUniqueRectangleType3B([]int{i, j}, pair, line2, cell, cands)
					return
				}
			}
		}
	}
	return
}

func logUniqueRectangleType3B(lineNums, cellNums []int, line House, cell *Cell, cands []int) {
	if line.HouseType() == 0 {
		log.Printf("r%v%vc%v%v r%v r%vc%v %v%v Unique Rectangle Type3B",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1], line.cells[0].rowNum,
			cell.rowNum, cell.colNum, cands[0]+1, cands[1]+1)
	} else {
		log.Printf("c%v%vr%v%v c%v r%vc%v %v%v Unique Rectangle Type3B",
			lineNums[0], lineNums[1], cellNums[0], cellNums[1], line.cells[0].colNum,
			cell.rowNum, cell.colNum, cands[0]+1, cands[1]+1)
	}
}

func (g *Grid) verifyUniqueRectangleType3B(line1 House, pair []int, line2 House) (ok bool, cell *Cell, cands []int) {
	biCands := line1.cells[pair[0]].cands
	cells := []*Cell{line2.cells[pair[0]], line2.cells[pair[1]]}
	if candAnd(biCands, cells[0].cands) != biCands || candAnd(biCands, cells[1].cands) != biCands {
		return
	}

	cands = intsFromBools(biCands)
	if cells[0].rowNum == cells[1].rowNum {
		cell = type3BHidden(biCands, cells, g.rows[cells[0].rowNum])
		if cell != nil {
			ok = true
			return
		}
	}
	if cells[0].colNum == cells[1].colNum {
		cell = type3BHidden(biCands, cells, g.cols[cells[0].colNum])
		if cell != nil {
			ok = true
			return
		}
	}

	if cells[0].boxNum == cells[1].boxNum {
		cell = type3BHidden(biCands, cells, g.boxes[cells[0].boxNum])
		if cell != nil {
			ok = true
			return
		}
	}

	return
}

func type3BHidden(cands [9]bool, cells []*Cell, house House) (hiddenCell *Cell) {
	count := 0
	for _, cell := range house.cells {
		if containCell(cells, cell) {
			continue
		}
		if len(intsFromBools(candAnd(cands, cell.cands))) != 0 {
			count++
			hiddenCell = cell
		}
	}
	if count == 1 {
		return
	}
	return nil
}
